package com.qwert.shortlink.project.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qwert.shortlink.project.common.convention.exception.ServiceException;
import com.qwert.shortlink.project.common.enums.VailDateTypeEnum;
import com.qwert.shortlink.project.dao.entity.ShortLink;
import com.qwert.shortlink.project.dao.entity.ShortLinkRouter;
import com.qwert.shortlink.project.dao.mapper.ShortLinkMapper;
import com.qwert.shortlink.project.dao.mapper.ShortLinkRouterMapper;
import com.qwert.shortlink.project.dto.req.shortlink.ShortLinkCreateReqDTO;
import com.qwert.shortlink.project.dto.req.shortlink.ShortLinkPageReqDTO;
import com.qwert.shortlink.project.dto.req.shortlink.ShortLinkUpdateReqDTO;
import com.qwert.shortlink.project.dto.resp.shortlink.ShortLinkCountRespDTO;
import com.qwert.shortlink.project.dto.resp.shortlink.ShortLinkCreateRespDTO;
import com.qwert.shortlink.project.dto.resp.shortlink.ShortLinkPageRespDTO;
import com.qwert.shortlink.project.service.IShortLinkService;
import com.qwert.shortlink.project.toolkit.HashUtil;
import com.qwert.shortlink.project.toolkit.LinkUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.redisson.api.RBloomFilter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.qwert.shortlink.project.common.constant.RedisKeyConstant.ROUTER_SHORT_LINK_KEY;

/**
 * @author qwert
 * @version 1.0.0
 * @create 2024/7/2 11:20
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLink> implements IShortLinkService {

    private final RBloomFilter<String> shortLinkCreateCachePenetrationBloomFilter;
    private final ShortLinkRouterMapper shortLinkRouterMapper;
    private final StringRedisTemplate stringRedisTemplate;
    // TODO
    private final String username = "zhangsan";

    @Override
    public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO shortLinkCreateReqDTO){

        // TODO 从用户上下文获取当前用户name；
//        String username = "zhangsan";

        // 根据原始链接地址和用户的name作为标识来生成短链接（布隆）
        String shortLinkSuffix = this.generateSuffix(shortLinkCreateReqDTO.getOriginUrl(), shortLinkCreateReqDTO.getDomain());
        String fullShortUrl = shortLinkCreateReqDTO.getDomain() + "/" + shortLinkSuffix;

        // 准备数据
        ShortLink shortLink = ShortLink.builder()
                .domain(shortLinkCreateReqDTO.getDomain())
                .originUrl(shortLinkCreateReqDTO.getOriginUrl())
                .gid(shortLinkCreateReqDTO.getGid())
                .createdType(shortLinkCreateReqDTO.getCreatedType())
                .validDateType(shortLinkCreateReqDTO.getValidDateType())
                .validDate(shortLinkCreateReqDTO.getValidDate())
                .describe(shortLinkCreateReqDTO.getDescribe())

                .shortUri(shortLinkSuffix)
                .fullShortUrl(fullShortUrl)
                .favicon(this.getFavicon(shortLinkCreateReqDTO.getOriginUrl()))
                .enableStatus(0)
                .build();

        // 准备短链接路由表
        ShortLinkRouter shortLinkRouter = ShortLinkRouter.builder()
                .fullShortUrl(fullShortUrl)
                .gid(shortLink.getGid())
                .build();

        // 插入数据库
        try {
            baseMapper.insert(shortLink);
            shortLinkRouterMapper.insert(shortLinkRouter);
        } catch (Exception e) {
            log.warn("短链接：{} 重复入库", fullShortUrl);
            shortLinkCreateCachePenetrationBloomFilter.add(fullShortUrl);
            throw new ServiceException("短链接生成重复");
        }

        // 缓存预热
        stringRedisTemplate.opsForValue().set(String.format(ROUTER_SHORT_LINK_KEY, fullShortUrl),
                shortLink.getOriginUrl(),
                LinkUtil.getLinkCacheValidTime(shortLinkCreateReqDTO.getValidDate()),
                TimeUnit.MILLISECONDS
        );
        // 将短链接数据存放到布隆过滤器中
        shortLinkCreateCachePenetrationBloomFilter.add(fullShortUrl);

        // 返回已添加的数据
        return ShortLinkCreateRespDTO.builder()
                .fullShortUrl(shortLink.getFullShortUrl())
                .originUrl(shortLink.getOriginUrl())
                .gid(shortLinkCreateReqDTO.getGid())
                .build();
    }

    @Override
    public Integer updateShortLink(ShortLinkUpdateReqDTO shortLinkUpdateReqDTO) {
        LambdaUpdateWrapper<ShortLink> updateWrapper = Wrappers.lambdaUpdate(ShortLink.class)
                .eq(ShortLink::getFullShortUrl, shortLinkUpdateReqDTO.getFullShortUrl())
                .eq(ShortLink::getGid, shortLinkUpdateReqDTO.getGid())
                .eq(ShortLink::getDelFlag, 0)
                .eq(ShortLink::getEnableStatus, 0)
                .set(Objects.equals(shortLinkUpdateReqDTO.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()), ShortLink::getValidDate, null);
        ShortLink shortLink = ShortLink.builder()
                .validDateType(shortLinkUpdateReqDTO.getValidDateType())
                .validDate(shortLinkUpdateReqDTO.getValidDate())
                .describe(shortLinkUpdateReqDTO.getDescribe())
                .enableStatus(shortLinkUpdateReqDTO.getEnableStatus())
                .build();
        return baseMapper.update(shortLink, updateWrapper);
    }

    /*@Override
    public void restoreUrl(String shortUri, ServletRequest request, ServletResponse response) throws IOException {
        String serverName = request.getServerName();
        String fullShortUrl = serverName + "/" + shortUri;
        // 将六位短链接分离，并验证布隆过滤器是否存在
        if(!shortLinkCreateCachePenetrationBloomFilter.contains(shortUri)){
            throw new ServiceException("用户访问的链接不存在");
        }

        // 短链接路由表查询出对应的gid
        LambdaQueryWrapper<ShortLinkRouter> routerWrapper = Wrappers.lambdaQuery(ShortLinkRouter.class)
                .eq(ShortLinkRouter::getFullShortUrl, fullShortUrl);
        ShortLinkRouter shortLinkRouter = shortLinkRouterMapper.selectOne(routerWrapper);
        String gid = shortLinkRouter.getGid();

        // 根据gid查询出原始链接
        LambdaQueryWrapper<ShortLink> queryLinkWrapper = Wrappers.lambdaQuery(ShortLink.class)
                .eq(ShortLink::getGid, gid);
        ShortLink shortLink = baseMapper.selectOne(queryLinkWrapper);
        if(shortLink == null){
            throw new ServiceException("原始链接错误，请重试");
        }

        // 重定向原始链接
        ((HttpServletResponse) response).sendRedirect(shortLink.getOriginUrl());

    }
*/
    @Override
    public IPage<ShortLinkPageRespDTO>  pageShortLink(ShortLinkPageReqDTO shortLinkPageReqDTO) {
        LambdaQueryWrapper<ShortLink> queryWrapper = Wrappers.lambdaQuery(ShortLink.class)
                .eq(ShortLink::getGid, shortLinkPageReqDTO.getGid())
                .eq(ShortLink::getEnableStatus, 0)
                .eq(ShortLink::getDelFlag, 0);

        IPage<ShortLink> resultPage = baseMapper.selectPage(shortLinkPageReqDTO, queryWrapper);

        return resultPage.convert(each -> BeanUtil.toBean(each, ShortLinkPageRespDTO.class));
    }

    @Override
    public List<ShortLinkCountRespDTO> listGroupShortLinkCount(List<String> gids) {
        QueryWrapper<ShortLink> queryWrapper = Wrappers.query(new ShortLink())
                .select("gid as gid, count(*) as shortLinkCount")
                .in("gid", gids)
                .eq("enable_status", 0)
                .eq("del_flag", 0)
                .eq("del_time", 0L)
                .groupBy("gid");
        List<Map<String, Object>> shortLinkList = baseMapper.selectMaps(queryWrapper);
        return BeanUtil.copyToList(shortLinkList, ShortLinkCountRespDTO.class);
    }


    /**
     * 短链接生成
     * @param originUrl 原始链接
     * @param domain  当前创建的所属域名
     * @return  六位短链接
     */
    private String generateSuffix(String originUrl, String domain) {
        String shortLinkSuffix = HashUtil.hashToBase62(originUrl);
        String fullShortUrl = domain + "/" + shortLinkSuffix;
        // 查询布隆过滤器判断新生成的短链接是否存在
        if(!shortLinkCreateCachePenetrationBloomFilter.contains(fullShortUrl)) {
            return shortLinkSuffix;
        }
        throw new ServiceException("用户生成短链接失败");
    }
    public String getFavicon(String url) {
        Document document = null;
        try {
            document = Jsoup.connect(url).get();
            Element iconLink = document.head().select("link[rel~=(?i)^(shortcut|icon|shortcut icon)$]").first();

            if (iconLink != null) {
                String iconHref = iconLink.attr("href");
                if (!iconHref.startsWith("http")) {
                    iconHref = url + (iconHref.startsWith("/") ? "" : "/") + iconHref;
                }
                return iconHref;
            }
        } catch (IOException e) {
            throw new ServiceException("获取网站图标失败");
        }
        return null;
    }

    /*private String generateSuffix(ShortLinkCreateReqDTO shortLinkCreateReqDTO) {
        int customGenerateCount = 0;
        String shortLinkSuffix;
        while(true) {
            if(customGenerateCount > 10) {
                throw new ServiceException("短链接频繁生成，请稍后重试");
            }
            String originUrl = shortLinkCreateReqDTO.getOriginUrl();
            // TODO 加上时间戳是否可取？
            originUrl += UUID.randomUUID().toString();
            shortLinkSuffix = HashUtil.hashToBase62(originUrl);
            log.info("短链接：{} 生成短链接：", shortLinkSuffix);

            if(!shortLinkCreateCachePenetrationBloomFilter.contains(shortLinkCreateReqDTO.getDomain() + "/" + shortLinkSuffix)){
                break;
            }
            customGenerateCount++;
        }
        return shortLinkSuffix;
    }*/
    /*@Override
    public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO shortLinkCreateReqDTO) {

        // 根据原始链接创建一个六位随机数
        String shortLinkSuffix = generateSuffix(shortLinkCreateReqDTO);
        String fullShortUrl = shortLinkCreateReqDTO.getDomain() + "/" + shortLinkSuffix;

        // 准备ShortLink的数据
        ShortLink shortLink = BeanUtil.toBean(shortLinkCreateReqDTO, ShortLink.class);
        shortLink.setShortUri(shortLinkSuffix);
        shortLink.setFullShortUrl(fullShortUrl);

        // 添加数据库时检查是否有误判
        try {
            baseMapper.insert(shortLink);
        } catch (Exception e) {
            // TODO 布隆误判的链接数据该如何处理
            // 布隆过滤器存在链接数据
            // 布隆过滤器不一定存在链接数据
            LambdaQueryWrapper<ShortLink> queryWrapper = Wrappers.lambdaQuery(ShortLink.class)
                    .eq(ShortLink::getFullShortUrl, fullShortUrl);
            ShortLink existingLink = baseMapper.selectOne(queryWrapper);
            if(existingLink != null) {
                // 记录存在无误叛
                log.warn("短链接：{} 重复入库", fullShortUrl);
                throw new ServiceException("短链接生成重复");
            }

        }

        // 将短链接数据存放到布隆过滤器中
        shortLinkCreateCachePenetrationBloomFilter.add(shortLinkSuffix);

        // 返回已添加的数据
        return ShortLinkCreateRespDTO.builder()
                .fullShortUrl(shortLink.getFullShortUrl())
                .originUrl(shortLink.getOriginUrl())
                .gid(shortLinkCreateReqDTO.getGid())
                .build();
    }*/
}
